/*
 * Copyright (C) 2008 Peter Kling
 * 
 * This file is part of 'A Kind of Billiard', a graphical billiard simulation.
 * 
 * 'A Kind of Billiard' is free software: you can redistribute it and/or modify it under the terms of the GNU General
 * Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
 * later version.
 * 
 * 'A Kind of Billiard' is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU General Public License along with 'A Kind of Billiard'. If not, see
 * <http://www.gnu.org/licenses/>.
 */

package de.akob.physics;

import de.akob.mathematics.MathVector;
import de.akob.mathematics.Mathematics;

/**
 * Defines basic methods and properties for physical objects.
 */
public class PhysicalObject {
	/**
	 * Types of bounding containers a rigid body can have. There is support for bounding boxes as well as for bounding
	 * spheres.
	 */
	public enum BoundingContainer {
		BOUNDING_BOX, BOUNDING_SPHERE
	}
	
	// properties of the rigid body
	public final double         mass;
	// protected double frictionCoefficient; // TODO: implement
	// state of a rigid body
	protected MathVector        position          = new MathVector(3);
	// protected MathQuaternion orientation = new MathQuaternion(..); // TODO: implement
	protected MathVector        velocity          = new MathVector(3);
	
	// protected MathVector angularVelocity = new MathVector(3); // TODO: uncomment when orientation is implemented
	protected MathVector        acceleration      = new MathVector(3);
	
	// data for bounding container (use for example for macro collision detection)
	protected BoundingContainer boundingContainer = BoundingContainer.BOUNDING_SPHERE;
	protected double            radius            = 0.;
	protected double            xMin              = 0., xMax = 0.;
	protected double            yMin              = 0., yMax = 0.;
	protected double            zMin              = 0., zMax = 0.;
	
	// miscellaneous member variables
	protected boolean           frozen            = false;
	
	/**
	 * Constructs a rigid body of the given mass. The resulting body will correspond to a point mass.
	 * 
	 * @param mass mass of object
	 */
	public PhysicalObject(double mass) {
		this.mass = mass;
		radius = 0.;
		boundingContainer = BoundingContainer.BOUNDING_SPHERE;
	}
	
	/**
	 * Constructs a rigid body sphere of the given mass and radius (centered at the origin).
	 * 
	 * @param mass mass of object
	 * @param radius radius of the rigid body sphere
	 */
	public PhysicalObject(double mass, double radius) {
		this.mass = mass;
		this.radius = radius;
		boundingContainer = BoundingContainer.BOUNDING_SPHERE;
	}
	
	/**
	 * Constructs a rigid body cuboid of the given mass and dimensions (centered at the origin).
	 * 
	 * @param mass mass of object
	 * @param xMin minimum x value
	 * @param xMax maximum x value
	 * @param yMin minimum y value
	 * @param yMax maximum y value
	 * @param zMin minimum z value
	 * @param zMax maximum z value
	 */
	public PhysicalObject(double mass, double xMin, double xMax, double yMin, double yMax, double zMin, double zMax) {
		this.mass = mass;
		this.xMin = xMin;
		this.xMax = xMax;
		this.yMin = yMin;
		this.yMax = yMax;
		this.zMin = zMin;
		this.zMax = zMax;
		boundingContainer = BoundingContainer.BOUNDING_BOX;
	}
	
	/**
	 * Applies the given acceleration to this body. Will have no effect if body is frozen.
	 * 
	 * @param acceleration acceleration to be applied
	 * @throws IllegalArgumentException if given acceleration vector does not have dimension three
	 */
	public void applyAcceleration(MathVector acceleration) {
		if (acceleration.dim != 3)
			throw new IllegalArgumentException("given acceleration vector must be of dimension three");
		if (frozen)
			return;
		this.acceleration.add(acceleration);
	}
	
	/**
	 * Applies the given force to this body. Will have no effect if body is frozen.
	 * 
	 * @param force force to be applied
	 * @throws IllegalArgumentException if given force vector does not have dimension three
	 */
	public void applyForce(MathVector force) {
		if (force.dim != 3)
			throw new IllegalArgumentException("given force vector must be of dimension three");
		if (frozen)
			return;
		this.acceleration.add(Mathematics.mul(1. / mass, force));
	}
	
	/**
	 * Applies the given momentum to this body. Will have no effect if body is frozen.
	 * 
	 * @param momentum momentum to be applied
	 * @throws IllegalArgumentException if given momentum vector does not have dimension three
	 */
	public void applyMomentum(MathVector momentum) {
		if (momentum.dim != 3)
			throw new IllegalArgumentException("given momentum vector must be of dimension three");
		if (frozen)
			return;
		velocity.add(Mathematics.mul(1. / mass, momentum));
	}
	
	/**
	 * Freezes the state of this body (it is not updated anymore). Thus, although for example collision handling will
	 * still regard this body, it will have no effect on the body's state.
	 * 
	 * @see unfreeze
	 */
	public void freeze() {
		frozen = true;
	}
	
	/**
	 * Returns the position vector of this body.
	 * 
	 * @return position vector of body
	 */
	public MathVector getPosition() {
		return position;
	}
	
	/**
	 * Returns the velocity vector of this body.
	 * 
	 * @return velocity vector of body
	 */
	public MathVector getVelocity() {
		return velocity;
	}
	
	/**
	 * Sets the position. This method will do nothing if body is frozen.
	 * 
	 * @param x x-coordinate of position
	 * @param y y-coordinate of position
	 * @param z z-coordinate of position
	 */
	public void setPosition(double x, double y, double z) {
		if (frozen)
			return;
		position.set(1, x);
		position.set(2, y);
		position.set(3, z);
	}
	
	/**
	 * Sets the position. This method will do nothing if body is frozen.
	 * 
	 * @param position positional vector
	 */
	public void setPosition(MathVector position) {
		if (position.dim != 3)
			throw new IllegalArgumentException("given positional vector must be of dimension three");
		if (frozen)
			return;
		this.position = position;
	}
	
	/**
	 * Sets the velocity. This method will do nothing if body is frozen.
	 * 
	 * @param v1 x-component of velocity
	 * @param v2 y-component of velocity
	 * @param v3 z-component of velocity
	 */
	public void setVelocity(double v1, double v2, double v3) {
		if (frozen)
			return;
		velocity.set(1, v1);
		velocity.set(2, v2);
		velocity.set(3, v3);
	}
	
	/**
	 * Sets the velocity. This method will do nothing if body is frozen.
	 * 
	 * @param velocity velocity vector
	 */
	public void setVelocity(MathVector velocity) {
		if (velocity.dim != 3)
			throw new IllegalArgumentException("given velocity vector must be of dimension three");
		if (frozen)
			return;
		this.velocity = velocity;
	}
	
	/**
	 * Unfreezes the state of this body (it is not updated anymore).
	 * 
	 * @see freeze
	 */
	public void unfreeze() {
		frozen = false;
	}
	
	/**
	 * Updates the position of this body according to the elapsed time <code>dt</code> and the current velocity. Will
	 * have no effect if body is frozen.
	 * 
	 * @param dt elapsed time (in seconds)
	 */
	public void updatePosition(double dt) {
		if (frozen)
			return;
		position.add(Mathematics.mul(dt, velocity));
	}
	
	/**
	 * Updates the velocity of this body according to the elapsed time <code>dt</code> and any force affecting the
	 * body. Will have no effect if body is frozen.
	 * 
	 * @param dt elapsed time (in seconds)
	 */
	public void updateVelocity(double dt) {
		if (frozen)
			return;
		velocity.add(Mathematics.mul(dt, acceleration));
	}
}
